package com.xzx.zookeeper.lock;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

import java.util.concurrent.CountDownLatch;

/**
 * 原生分布式锁,一个JVM只需要一个这个对象即可
 * @version 1.0
 * @auther xinzhixuan
 * @date 2022/9/3 13:01
 */
public class DistributeLock {
    private ZooKeeper zooKeeper;
    private String currentLockPath;
    private String previousLockPath;
    private Watcher watcher;
    private CountDownLatch lockWaitLatch = new CountDownLatch(1);


    public DistributeLock(ZkClient zkClient) {
        this.zooKeeper = zkClient.getZooKeeper();
        this.watcher = new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                // waitLatch  需要释放
                if (event.getType()== Event.EventType.NodeDeleted && event.getPath().equals(previousLockPath)){
                    lockWaitLatch.countDown();
                }

            }
        };
    }


    /**
     * 获取锁，获取不到就会阻塞在这里
     */
    public void lock() {
        try {
            // 创建临时带序号的节点，也就是先占一把锁，临时节点的原因是，程序挂掉也可以释放锁，带序号的原因是只需要监听前一把锁即可
            currentLockPath = zooKeeper.create("/locks/lock", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println("===currentLockPath=" + currentLockPath);
            previousLockPath = "/locks/lock" + String.format("%010d", Long.parseLong(currentLockPath.split("/locks/lock")[1]) - 1);
            System.out.println("===previousLockPath=" + previousLockPath);
            zooKeeper.getData(previousLockPath, this.watcher, new Stat());
            lockWaitLatch.await();
            System.out.println("==lock success, currentLockPath=" + currentLockPath + "wait previous unlock success");
        } catch (KeeperException e) {
            if (e.getPath().equals(previousLockPath) && e instanceof KeeperException.NoNodeException) {
                // 说明前一把锁已经不存在了，所以可以获取到锁
                System.out.println("==lock success currentLockPath=" + currentLockPath + ", previous no lock");
            } else {
                e.printStackTrace();
            }
        } catch (InterruptedException e) {
            System.out.println("lock error");
            e.printStackTrace();
        }
    }

    /**
     * 业务完成，释放锁
     */
    public void unlock() {
        try {
            this.zooKeeper.delete(currentLockPath, -1);
            System.out.println("unlock success currentLockPath=" + currentLockPath);
        } catch (InterruptedException|KeeperException  e) {
            System.out.println("currentLockPath=" + currentLockPath + " unlock error");
            e.printStackTrace();
        }
    }
}
